package spoon.bet365.service;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.TimeZone;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.transaction.annotation.Transactional;

import spoon.bet365.dao.Bet365Dao;
import spoon.bet365.domain.Game;
import spoon.bet365.domain.League;
import spoon.common.exception.ConvertException;
import spoon.domain.Betting;
import spoon.service.HttpService;

@Transactional
public abstract class Bet365ServiceImpl implements Bet365Service {
	
	@Autowired
	protected HttpService httpService;
	
	@Autowired
	protected Bet365Dao bet365Dao;
	
	@Value("${bet365.domain}")
	private String domain;
	
	protected Logger log = LoggerFactory.getLogger(this.getClass());

	/**
	 * 리그 정보를 가져온다. 각 종목마다 새로 작성해야 한다.
	 */
	@Override
	public abstract List<League> getLeague();

	/**
	 * 게임을 가져온다. 각 종목마다 새로 작성해야 한다.
	 */
	@Override
	public abstract void getGame(League league);
	
	/**
	 * 리그별 게임을 가져온다.
	 * 
	 * @param league
	 * @return
	 */
	protected abstract void getGameList(League league);
	
	/**
	 * 게임별 베팅을 가져온다.
	 * 
	 * @param game
	 * @return
	 */
	protected abstract void getBettingList(Game game);
	
	/**
	 * 문자열에서 key에 대응하는 value 값을 뽑아온다.
	 * key가 없으면 공백을 반환한다.
	 * 
	 * @param token
	 * @return
	 */	
	protected String getTokenName(String token, String key) {
		int pos = token.indexOf(key + "=");
		
		if (pos < 0) {
			return "";
		}
		
		token = token.substring(pos + 3);
		token = token.substring(0, token.indexOf(";"));
		return token;
	}

	/**
	 * 리그별 기본 데이터 링크
	 * 
	 * @param url
	 * @return
	 */
	protected String getGameLink(String url) {
		String source = httpService.getSource(convertLink(url));
		String link = getTokenName(source, "PD");
		return convertLink(link);
	}

	/**
	 * 링크 parameter를 실제 접속가능한 full 주소로 변경한다.
	 * 
	 * @param link
	 * @return
	 */
	protected String convertLink(String link) {
		return this.domain + link.replaceAll("#", "%23");
	}
	
	/**
	 * 날짜 변환 표준시간으로 변환
	 * 
	 * @param dt
	 * @return
	 */
	protected Date convertDate(String dt) {
		DateFormat df = new SimpleDateFormat("yyyyMMddHHmmss");
		TimeZone tz = TimeZone.getTimeZone("Greenwich");
		df.setTimeZone(tz);

		Date date = new Date();
		try {
			date = df.parse(dt);
		} catch (ParseException e) {
			throw new ConvertException(new Throwable("BET365 - 날짜변경 에러 (" + dt + ")" , e));
		}
		return date;
	}
	
	/**
	 * 배당변환 분수 형태를 소수점 형태로
	 * 
	 * @param rate
	 * @return
	 */
	protected Double convertRate(String rate) {
		String[] s = rate.split("/");
		
		if (s.length != 2) {
			throw new ConvertException(new Throwable("BET365 배당율 변경 에러"));
		}

		Double d = 1D + (Double.parseDouble(s[0]) / Double.parseDouble(s[1]));
		return Double.parseDouble(String.format("%.2f", d - 0.005));
	}
	
	/**
	 * 핸디캡 변환 
	 * 
	 * @param handicap
	 * @return
	 */
	protected Double convertHandicap(String handicap) {
		
		Double d = null;
		if (handicap == null || "".equals(handicap)) {
			d = 0D;
		} else if (handicap.contains(",")) {
			String[] hs = handicap.split(",");
			d = (Double.parseDouble(hs[0]) + Double.parseDouble(hs[1])) / 2D;
		} else {
			d = Double.parseDouble(handicap);
		}
		
		return d;
	}
	
	/**
	 * token을 key, value로 Map으로 변환
	 * 
	 * @param token
	 * @return
	 */
	protected Map<String, String> tokenMap(String token) {

		Map<String, String> tokenMap = new HashMap<String, String>();
		String[] tokenParam = token.split(";");

		for (String param : tokenParam) {
			String[] params = param.split("=");
			tokenMap.put(params[0], params.length > 1 ? params[1] : "");
		}

		return tokenMap;
	}
	
	/**
	 * Over/Under, Handicap 2개 한셋트
	 * 
	 * @param game
	 * @param map
	 */
	protected void getHandicap(Game game, Map<String, String> map) {
		Betting betting = null;
		int order = 0;
		
		String source = httpService.getSource(map.get("link"));
		StringTokenizer line = new StringTokenizer(source, "|");
		
		while (line.hasMoreElements()) {
			String token = line.nextToken();
			
			if (token.startsWith("PA")) {
				if (order % 2 == 0) {
					betting = new Betting();
					betting.setBettingScore(map.get("bettingScore"));
					betting.setBettingType(map.get("bettingType"));
					betting.setBettingSet(map.get("bettingSet"));
					betting.setBettingCode(map.get("bettingCode"));
					betting.setBettingName(map.get("bettingName"));
					betting.setHandicapDescr(getTokenName(token, "HD"));
					betting.setHandicap(convertHandicap(getTokenName(token, "HA")));
					betting.setHomeRate(convertRate(getTokenName(token, "OD")));
					betting.setHomeDescr(getTokenName(token, "NA"));
					betting.setDrawRate(0D);
					betting.setDrawDescr("");
				} else {
					betting.setAwayRate(convertRate(getTokenName(token, "OD")));
					betting.setAwayDescr(getTokenName(token, "NA"));
					game.getBettingList().add(betting);
				}
				order++;
			}
		}
	}
	
	/**
	 * 승무패 등 3개 한셋트
	 * 
	 * @param game
	 * @param map
	 */
	protected void getMatch(Game game, Map<String, String> map) {
		Betting betting = null;
		int order = 0;
		
		String source = httpService.getSource(map.get("link"));
		StringTokenizer line = new StringTokenizer(source, "|");
		
		while (line.hasMoreElements()) {
			String token = line.nextToken();
			
			if (token.startsWith("PA")) {
				if (order % 3 == 0) {
					betting = new Betting();
					betting.setBettingScore(map.get("bettingScore"));
					betting.setBettingType(map.get("bettingType"));
					betting.setBettingSet(map.get("bettingSet"));
					betting.setBettingCode(map.get("bettingCode"));
					betting.setBettingName(map.get("bettingName"));
					betting.setHandicapDescr(getTokenName(token, "HD"));
					betting.setHandicap(convertHandicap(getTokenName(token, "HA")));
					betting.setHomeRate(convertRate(getTokenName(token, "OD")));
					betting.setHomeDescr(getTokenName(token, "NA"));
				} else if (order % 3 == 1) {
					betting.setDrawRate(convertRate(getTokenName(token, "OD")));
					betting.setDrawDescr(getTokenName(token, "NA"));
				} else {
					betting.setAwayRate(convertRate(getTokenName(token, "OD")));
					betting.setAwayDescr(getTokenName(token, "NA"));
					game.getBettingList().add(betting);
				}
				
				order++;
			}
		}
	}
	
	/**
	 * 메인페이지 3게임 가져오는것
	 * 
	 * @param home
	 * @param away
	 * @param betting
	 * @return
	 */
	protected Betting getMainGame(String home, String away, Betting betting) {
		betting.setHandicap(convertHandicap(getTokenName(home, "HA")));
		betting.setHandicapDescr(getTokenName(home, "NA"));
		betting.setHomeRate(convertRate(getTokenName(home, "OD")));
		betting.setDrawRate(0D);
		betting.setAwayRate(convertRate(getTokenName(away, "OD")));
		betting.setHomeDescr(getTokenName(home, "NA"));
		betting.setDrawDescr("");
		betting.setAwayDescr(getTokenName(away, "NA"));
		
		return betting;
	}
	
	/**
	 * 순서가 없이 2 조합되는 경기 (첫경기와 마지막 경기가 한조합)
	 * 
	 * @param game
	 * @param map
	 */
	protected void getSpreadGame(Game game, Map<String, String> map) {
		List<String> tokenList = new ArrayList<>();
		Betting betting = null;
		
		String source = httpService.getSource(map.get("link"));
		StringTokenizer line = new StringTokenizer(source, "|");
		
		// PA만 뽑아서 넣는다.
		while (line.hasMoreElements()) {
			String token = line.nextToken();
			if (token.startsWith("PA")) {
				tokenList.add(token);
			}
		}
		
		// 처음과 마지막이 한셋이다.
		while (tokenList.size() > 0) {
			String away = tokenList.remove(tokenList.size() - 1);
			String home = tokenList.remove(0);
			
			betting = new Betting();
			betting.setBettingScore(map.get("bettingScore"));
			betting.setBettingType(map.get("bettingType"));
			betting.setBettingSet(map.get("bettingSet"));
			betting.setBettingCode(map.get("bettingCode"));
			betting.setBettingName(map.get("bettingName"));
			betting.setHandicapDescr(getTokenName(home, "HA"));
			betting.setHandicap(convertHandicap(getTokenName(home, "HA")));
			betting.setHomeRate(convertRate(getTokenName(home, "OD")));
			betting.setHomeDescr(getTokenName(home, "NA"));
			betting.setDrawRate(0D);
			betting.setDrawDescr("");
			betting.setAwayRate(convertRate(getTokenName(away, "OD")));
			betting.setAwayDescr(getTokenName(away, "NA"));
			
			game.getBettingList().add(betting);
		}

	}
	
	/**
	 * 순서가 없이 2 조합되는 경기  (첫경기와 중간경기가 한조합)
	 * 
	 * @param game
	 * @param map
	 */
	protected void getLineGame(Game game, Map<String, String> map) {
		List<String> tokenList = new ArrayList<>();
		Betting betting = null;
		
		String source = httpService.getSource(map.get("link"));
		StringTokenizer line = new StringTokenizer(source, "|");
		
		// PA만 뽑아서 넣는다.
		while (line.hasMoreElements()) {
			String token = line.nextToken();
			if (token.startsWith("PA")) {
				tokenList.add(token);
			}
		}
		
		int size = tokenList.size() / 2;
		
		// 처음과 중간이 한조합이다.
		while (tokenList.size() > 0) {
			String away = tokenList.remove(size);
			String home = tokenList.remove(0);
			
			betting = new Betting();
			betting.setBettingScore(map.get("bettingScore"));
			betting.setBettingType(map.get("bettingType"));
			betting.setBettingSet(map.get("bettingSet"));
			betting.setBettingCode(map.get("bettingCode"));
			betting.setBettingName(map.get("bettingName"));
			betting.setHandicapDescr(getTokenName(home, "HA"));
			betting.setHandicap(convertHandicap(getTokenName(home, "HA")));
			betting.setHomeRate(convertRate(getTokenName(home, "OD")));
			betting.setHomeDescr(getTokenName(home, "NA"));
			betting.setDrawRate(0D);
			betting.setDrawDescr("");
			betting.setAwayRate(convertRate(getTokenName(away, "OD")));
			betting.setAwayDescr(getTokenName(away, "NA"));
			
			game.getBettingList().add(betting);
			size--;
		}

	}
	
	/**
	 * 승패무 등 3개 한셋트
	 * 
	 * @param game
	 * @param map
	 */
	protected void getWinXLose(Game game, Map<String, String> map) {
		Betting betting = null;
		int order = 0;
		
		String source = httpService.getSource(map.get("link"));
		StringTokenizer line = new StringTokenizer(source, "|");
		
		while (line.hasMoreElements()) {
			String token = line.nextToken();
			
			if (token.startsWith("PA")) {
				if (order % 3 == 0) {
					betting = new Betting();
					betting.setBettingScore(map.get("bettingScore"));
					betting.setBettingType(map.get("bettingType"));
					betting.setBettingSet(map.get("bettingSet"));
					betting.setBettingCode(map.get("bettingCode"));
					betting.setBettingName(map.get("bettingName"));
					betting.setHandicapDescr("");
					betting.setHandicap(0D);
					betting.setHomeRate(convertRate(getTokenName(token, "OD")));
					betting.setHomeDescr(getTokenName(token, "NA"));
				} else if (order % 3 == 1) {
					betting.setAwayRate(convertRate(getTokenName(token, "OD")));
					betting.setAwayDescr(getTokenName(token, "NA"));
				} else {
					betting.setDrawRate(convertRate(getTokenName(token, "OD")));
					betting.setDrawDescr(getTokenName(token, "NA"));
					game.getBettingList().add(betting);
				}
				
				order++;
			}
		}
	}
	
	/**
	 * 게임 등록하기
	 * 
	 */
	protected Integer addBetting(League league) {
		bet365Dao.delete(league);
		if (league.getGameList().size() == 0) {
			return 0;
		}
		return bet365Dao.addBetting(league);
	}
}
